
#import "GameManager.h"

float W=320;
float H=480;

@implementation GameManager

#pragma mark ============================= Init Methods ================================

+ (GameManager*) getInstance  {
    
    static GameManager* gameManager;
		
	if (!gameManager) {
        gameManager = [[GameManager alloc] init];
		[gameManager preloader];            
	}
	    
    return gameManager;
}

- (void) preloader {
    sprites = [[NSMutableArray alloc] initWithCapacity:20];
    newSprites = [[NSMutableArray alloc] initWithCapacity:20];
    destroyableSprites = [[NSMutableArray alloc] initWithCapacity:20];
   
    //Preload OGL-Textures          
    [self getTex: @"asteroid.png" isImage: YES];   
    [self getTex: @"obstacle.png" isImage: YES];
    [self getTex: @"cockpit.png" isImage: YES];
    
    //Optional
    //[self getTex: @"abc" isImage: NO]; 
    
    [self setOGLProjection];
    
    state = LOAD_GAME;
}

- (void) loadGame { 
    [sprites removeAllObjects];
    [newSprites removeAllObjects];
    [destroyableSprites removeAllObjects];      
    
    //Optional
    //[self removeFromDictionary: @"myTex1.png"];
 
    [self createSprite: PLAYER];
       
    for (int i=0; i<20; i++) {
        [self createSprite: ASTEROID];
    }      
    for (int i=0; i<10; i++) {
        [self createSprite: OBSTACLE];
    }     
    for (int i=0; i<5; i++) {
        [self createSprite: CAPSULE];
    }             
}

- (id) createSprite: (int) type {
    
    Player * p = [self getPlayer];
    Vertex posPlayer = [p getPos]; 
    
    Vertex pos;
    pos.x = [self getRndBetween: posPlayer.x - zFar/4 
                            and: posPlayer.x + zFar/4]; 
    pos.y = [self getRndBetween: posPlayer.y - zFar/2 
                            and: posPlayer.y + zFar/2]; 
    pos.z = [self getRndBetween: posPlayer.z + zFar+10 
                            and: posPlayer.z + zFar+50]; 
    
    Vertex speed;
    speed.x = 0; speed.y = 0; speed.z = 0;
    
    Vertex rotation;
    rotation.x = 1; rotation.y = 1; rotation.z = 1;
    
    if (type == ASTEROID) {         
        Asteroid *asteroid = [[Asteroid alloc] init];                   
        [asteroid setPos: pos]; 
        speed.z = -20;
        [asteroid setSpeed: speed];
        [asteroid setRotationStep: [self getRndBetween: -10 and: 10]
                      aroundAxis: rotation];
        [newSprites addObject: asteroid];
        [asteroid release];
        return asteroid;        
    } else if (type == OBSTACLE) {         
        Obstacle *obstacle = [[Obstacle alloc] init];                  
        [obstacle setPos: pos]; 
        [obstacle setSpeed: speed];
        [obstacle setRotationStep: [self getRndBetween: -5 and: 5]
                      aroundAxis: rotation];
        [newSprites addObject: obstacle];
        [obstacle release];
        return obstacle;          
    } else if (type == CAPSULE) {         
        Capsule *capsule = [[Capsule alloc] init]; 
        [capsule setPos: pos];
        speed.z = 2;
        [capsule setSpeed: speed];
        [capsule setRotationStep: [self getRndBetween: -10 and: 10]
                      aroundAxis: rotation];
        [newSprites addObject: capsule];
        [capsule release];
        return capsule;           
    } else if (type == PLAYER) {         
        Player *player = [[Player alloc] init];     
        [newSprites addObject: player];
        [player release];
        return player;         
    } else if (type == BULLET) {         
        pos.y = posPlayer.y-40; 
        pos.z = posPlayer.z;
        speed.z = 100;
        
        //Left Bullet
        Bullet *bulletL = [[Bullet alloc] init];                
        pos.x = posPlayer.x+20;        
        [bulletL setPos: pos];        
        [bulletL setSpeed: speed];        
        [newSprites addObject: bulletL];
        [bulletL release];
        
        //Right Bullet
        Bullet *bulletR = [[Bullet alloc] init];        
        pos.x = posPlayer.x-20;       
        [bulletR setPos: pos];        
        [bulletR setSpeed: speed];        
        [newSprites addObject: bulletR];
        [bulletR release];      
    } else {          
        NSLog(@"ERROR: Unknown sprite type: %i", type); 
        return nil;
    }    
    return nil;
}

#pragma mark ============================= Game Handler ===============================

- (Player *) getPlayer {
    for (Sprite *sprite in sprites) { 
        if ([sprite isActive]) {             
            if ([sprite getType] == PLAYER) {
                return (Player *) sprite;
            } 
        }   
    }        
    return nil;
}

- (void) touchBegan: (CGPoint) p {      
    [self handleStates];
    if (state == PLAY_GAME) {
        for (Sprite *sprite in sprites) { 
            if ([sprite isActive]) {             
                if ([sprite getType] == PLAYER) {
                    [(Player *) sprite setTouch: p];
                } 
            }   
        } 
    }    
}

- (void) touchMoved: (CGPoint) p {
    if (state == PLAY_GAME) {
        [self touchBegan: p];
    }    
}

- (void) touchEnded {
    if (state == PLAY_GAME) {
        for (Sprite *sprite in sprites) { 
            if ([sprite isActive]) {             
                if ([sprite getType] == PLAYER) {
                    [(Player *) sprite touchEnded];
                } 
            }   
        }           
    }    
}

- (void) handleStates {   
    if (state == GAME_OVER) {
        state = LOAD_GAME;
    }
}

- (void) drawStatesWithFrame: (CGRect) frame { 
    W = frame.size.width;
    H = frame.size.height; 
    switch (state) {
        case LOAD_GAME: 
            [self loadGame];            
            [self createOGLText: @"GOOD LUCK" 
                      offsetX: 2
                      offsetY: 2 
                  selfDestroy: YES];
            state = PLAY_GAME;
            break;  
        case PLAY_GAME:
            [self playGame];
            break;
        case GAME_OVER:
            [self playGame];                                  
            break;             
        default: NSLog(@"ERROR: Unknown state: %i", state);
            break;
    }    
}	 

- (void) playGame {
    timer++;       
        
    if (timer % 5 == 0) {
        [self createSprite: ASTEROID]; 
    }  
    if (timer % 20 == 0) {
        [self createSprite: OBSTACLE]; 
    }  
    if (timer % 80 == 0) {           
        [self createSprite: CAPSULE];    
    }       
   
    [self manageSprites];             
    [self renderSprites];          
} 

- (Vertex) getViewportOrigin {    
    Vertex v = [[self getPlayer] getPos];         
    return v;    
}

- (void) checkSprite: (Sprite *) sprite {
    Player *player = [self getPlayer];
    if ([sprite getType] == ASTEROID || [sprite getType] == OBSTACLE) {             
        if ([player checkColWithSprite: sprite]) {
            [self createOGLText: @"DAMAGED!!" 
                        offsetX: 2
                        offsetY: 1 
                    selfDestroy: YES];
            [sprite hit];
            [player hit];
        }
    }  
    if ([sprite getType] == OBSTACLE) {             
        for (Sprite *bullet in sprites) {             
            if ([bullet getType] == BULLET) {
                if ([bullet checkColWithSprite: sprite]) {
                    [self createOGLText: @"DESTROYED" 
                                offsetX: 2
                                offsetY: 1 
                            selfDestroy: YES];
                    [sprite hit];
                    [bullet hit];                    
                } 
            }  
        }  
    }  
    if ([sprite getType] == CAPSULE) {             
        if ([player checkColWithSprite: sprite]) {
            [self createOGLText: @"GOOOOOD!!" 
                        offsetX: 2
                        offsetY: 1 
                    selfDestroy: YES];
            [sprite hit];
            [player hitCapsule];
        } 
    }  
}

#pragma mark ============================= OGL Methods ===============================

- (void) setOGLProjection {    
    glLoadIdentity();
    
    //Set View
    glMatrixMode(GL_PROJECTION);     
    zNear = 0.1; 
    zFar = 2500;    
    fieldOfViewAngle = 45;
    float top = zNear * tan(M_PI * fieldOfViewAngle / W);
    float bottom = -top;
    float left = bottom * W / H; 
    float right = top * W / H;    
    glFrustumf(left, right, bottom, top, zNear, zFar); 
    
    glMatrixMode(GL_MODELVIEW);
    glEnableClientState(GL_VERTEX_ARRAY); 
    glEnableClientState(GL_NORMAL_ARRAY); 
    glEnable(GL_DEPTH_TEST); 
    glDepthFunc(GL_LESS);
    
    [self setLight];
} 

- (void) setLight {    
    glEnable(GL_LIGHTING);     
    glEnable(GL_COLOR_MATERIAL); 
    
    glEnable(GL_LIGHT0); 
    GLfloat color0[ ] = {1, 1, 1, 1};    
    glLightfv(GL_LIGHT0, GL_DIFFUSE, color0);     
    GLfloat light_position0[ ] = {0, 1, 0, 0};  
    glLightfv(GL_LIGHT0, GL_POSITION, light_position0); 
   
    glEnable(GL_LIGHT1);
    GLfloat color1[ ] = {1, 1, 1, 1};
    glLightfv(GL_LIGHT1, GL_DIFFUSE, color1);  
    GLfloat light_position1[ ] = {0, 0, -1, 0}; 
    glLightfv(GL_LIGHT1, GL_POSITION, light_position1); 
}

- (Tex *) getTex: (NSString*) name isImage: (bool) imgFlag { 
	Tex *tex = [[self getDictionary] objectForKey: name];
	if (!tex) {
		tex = [[Tex alloc] init];
        if (imgFlag) {            
            [tex createTexFromImage: name];             
        } else {
            [tex createTexFromString: name];
        }    
        [[self getDictionary] setObject: tex forKey: name];
        NSLog(@"%@ stored in dictionary as Tex.", name);
        [tex release];         
	}          
	return tex;  
}

- (id) createOGLText: (NSString *) txt 
           offsetX: (float) x
           offsetY: (float) y 
       selfDestroy: (bool) destroy {
    
    Text *text = [[Text alloc] init];   
    
    [text setText: txt 
          offsetX: x
          offsetY: y
      selfDestroy: destroy];    
    
    [newSprites addObject: text];
    [text release];
    return text;
}

//Camera algorithm based on mesa3d.org (Open-Source Mesa 3D Graphics Library)
- (void) setCameraX: (GLfloat) camX 
            cameraY: (GLfloat) camY 
            cameraZ: (GLfloat) camZ
            lookAtX: (GLfloat) atX 
            lookAtY: (GLfloat) atY 
            lookAtZ: (GLfloat) atZ {
    
    glLoadIdentity();
    
    GLfloat matrix[16]; 
    
    GLfloat forward[3]; 
    GLfloat upward[3]; 
    GLfloat sideward[3];           
    
    upward[0] = 0; //x
    upward[1] = 1; //y
    upward[2] = 0; //z
    //[self normalizeVector: upward]; 
    
    forward[0] = camX - atX;
    forward[1] = camY - atY;
    forward[2] = camZ - atZ;    
    [self normalizeVector: forward];
    
    sideward[0] =  upward[1] * forward[2] - upward[2] * forward[1];
    sideward[1] = -upward[0] * forward[2] + upward[2] * forward[0];
    sideward[2] =  upward[0] * forward[1] - upward[1] * forward[0];
    
    upward[0] =  forward[1] * sideward[2] - forward[2] * sideward[1];
    upward[1] = -forward[0] * sideward[2] + forward[2] * sideward[0];
    upward[2] =  forward[0] * sideward[1] - forward[1] * sideward[0];
    
    [self normalizeVector: sideward];
    [self normalizeVector: forward];
    
    matrix[0] = sideward[0];
    matrix[1] = sideward[1];
    matrix[2] = sideward[2];
    matrix[3] = 0;
    
    matrix[4] = upward[0];
    matrix[5] = upward[1];
    matrix[6] = upward[2];
    matrix[7] = 0;
    
    matrix[8] = forward[0];
    matrix[9] = forward[1];
    matrix[10] = forward[2];    
    matrix[11] = 0;
    
    matrix[12] = 0;
    matrix[13] = 0;
    matrix[14] = 0;
    matrix[15] = 1;
    
    glMultMatrixf(matrix);
    
    glTranslatef(-camX, -camY, -camZ);    
}

- (void) normalizeVector: (GLfloat [3]) v {
    float length = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    
    if (length != 0) { 
        v[0] /= length;
        v[1] /= length;
        v[2] /= length;
    }
}

#pragma mark ============================= Helper Methods ===============================

- (void) setState: (int) stt {
    state = stt;
}

- (int) getState {
    return state;
}

- (float) getzFar {
    return zFar;
}

- (void) manageSprites {
    //NSLog(@"Sprites: %i destroyable: %i new: %i", [sprites count], [destroyableSprites count], [newSprites count]);
    
    for (Sprite *destroyableSprite in destroyableSprites) { 
        for (Sprite *sprite in sprites) { 
            if (destroyableSprite == sprite) { 
                [sprites removeObject: sprite];
                break;
            }
        }   
    }  
    
    for (Sprite *newSprite in newSprites){ 
        [sprites addObject: newSprite];   
    } 
    
    [destroyableSprites removeAllObjects]; 
    [newSprites removeAllObjects];
}

- (void) renderSprites {
    for (Sprite *sprite in sprites) { 
        if ([sprite isActive]) {             
            [self checkSprite: sprite];
            if ([sprite getType] != PLAYER && [sprite getType] != TEXT) {
                [sprite draw]; 
            }    
        } else {
            [destroyableSprites addObject: sprite]; 
        }    
    }         

    for (Sprite *sprite in sprites) {             
        if ([sprite getType] == TEXT) {
            [sprite draw]; 
        }  
    }      
    [[self getPlayer] draw];         
}

- (NSMutableDictionary *) getDictionary {
	if (!dictionary) { 
		dictionary = [[NSMutableDictionary alloc] init]; 
	}
	return dictionary;
}

- (void) removeFromDictionary: (NSString*) name {
    [[self getDictionary] removeObjectForKey: name];
} 

- (int) getRndBetween: (int) bottom and: (int) top {		
	int rnd = bottom + (arc4random() % (top+1-bottom)); 
	return rnd;
} 

- (void) dealloc {        
    [sprites release];
    [newSprites release];
    [destroyableSprites release];
    [dictionary release];
    [super dealloc];
}

@end
